﻿/* 
* Copyright (c) 2010, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using Bio.Data.Interfaces;
using Bio.Views.Structure.Editor.Internal.Editing;
using Bio.Views.Structure.Editor.Internal.Rendering.Interfaces;
using Bio.Views.Structure.Editor.Internal.Rendering.Primitives;

namespace Bio.Views.Structure.Editor.Internal.Rendering.Visuals
{
    public class HelixStructureElementVisualComponent : DependencyObject, IStructureElementVisualComponent
    {
        #region Dependency Properties

        public static readonly DependencyProperty NucleotideFontFamilyProperty = DependencyProperty.Register("NucleotideFontFamily",
            typeof(FontFamily), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFontWeightProperty = DependencyProperty.Register("NucleotideFontWeight",
            typeof(FontWeight?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFontStyleProperty = DependencyProperty.Register("NucleotideFontStyle",
            typeof(FontStyle?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFontSizeProperty = DependencyProperty.Register("NucleotideFontSize",
            typeof(double?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideRenderingStyleProperty = DependencyProperty.Register("NucleotideRenderingStyle",
            typeof(NucleotideRenderingMode?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFillProperty = DependencyProperty.Register("NucleotideFill",
            typeof(Brush), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty IsNucleotideSubstitutedProperty = DependencyProperty.Register("IsNucleotideSubstituted",
           typeof(bool?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideSubstitutionProperty = DependencyProperty.Register("NucleotideSubstitution",
            typeof(char?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleDiameterProperty = DependencyProperty.Register("NucleotideCircleDiameter",
            typeof(double?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleEdgeThicknessProperty = DependencyProperty.Register("NucleotideCircleEdgeThickness",
            typeof(double?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleIsFilledProperty = DependencyProperty.Register("NucleotideCircleIsFilled",
            typeof(bool?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleFillProperty = DependencyProperty.Register("NucleotideCircleFill",
            typeof(Brush), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleStrokeProperty = DependencyProperty.Register("NucleotideCircleStroke",
            typeof(Brush), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty BasePairConnectorRenderingStyleProperty = DependencyProperty.Register("BasePairConnectorRenderingStyle",
           typeof(BasePairConnectorRenderingMode?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty BasePairConnectorStrokeProperty = DependencyProperty.Register("BasePairConnectorStroke",
            typeof(Brush), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty BasePairConnectorStrokeThicknessProperty = DependencyProperty.Register("BasePairConnectorStrokeThickness",
            typeof(double?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty BasePairConnectorSizeProperty = DependencyProperty.Register("BasePairConnectorSize",
            typeof(double?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        /*public static readonly DependencyProperty BasePairConnectorOffsetProperty = DependencyProperty.Register("BasePairConnectorOffset",
            typeof(double?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata());*/

        public static readonly DependencyProperty BasePairConnectorFillProperty = DependencyProperty.Register("BasePairConnectorFill",
            typeof(Brush), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty BasePairConnectorIsVisibleProperty = DependencyProperty.Register("BasePairConnectorIsVisible",
            typeof(bool?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty BasePairConnectorIsFilledProperty = DependencyProperty.Register("BasePairConnectorIsFilled",
            typeof(bool?), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        private static readonly DependencyPropertyKey IsPseudoknottedPropertyKey = DependencyProperty.RegisterReadOnly("IsPseudoknotted",
            typeof(bool), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty IsPseudoknottedProperty = IsPseudoknottedPropertyKey.DependencyProperty;

        public bool IsPseudoknotted
        {
            get { return (bool)GetValue(IsPseudoknottedProperty); }
            private set { SetValue(IsPseudoknottedPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey BasePairsPropertyKey = DependencyProperty.RegisterReadOnly("BasePairs",
            typeof(IEnumerable<IBasePairBioEntity>), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty BasePairsProperty = BasePairsPropertyKey.DependencyProperty;

        public IEnumerable<IBasePairBioEntity> BasePairs
        {
            get { return (IEnumerable<IBasePairBioEntity>)GetValue(BasePairsProperty); }
            private set { SetValue(BasePairsPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey FivePrimeStrandPropertyKey = DependencyProperty.RegisterReadOnly("FivePrimeStrand",
            typeof(IEnumerable<IBioSymbol>), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty FivePrimeStrandProperty = FivePrimeStrandPropertyKey.DependencyProperty;

        public IEnumerable<IBioSymbol> FivePrimeStrand
        {
            get { return (IEnumerable<IBioSymbol>)GetValue(FivePrimeStrandProperty); }
            private set { SetValue(FivePrimeStrandPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey ThreePrimeStrandPropertyKey = DependencyProperty.RegisterReadOnly("ThreePrimeStrand",
            typeof(IEnumerable<IBioSymbol>), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty ThreePrimeStrandProperty = ThreePrimeStrandPropertyKey.DependencyProperty;

        public IEnumerable<IBioSymbol> ThreePrimeStrand
        {
            get { return (IEnumerable<IBioSymbol>)GetValue(ThreePrimeStrandProperty); }
            private set { SetValue(ThreePrimeStrandPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey FivePrimeStartNucleotideIndexPropertyKey = DependencyProperty.RegisterReadOnly("FivePrimeStartNucleotideIndex",
            typeof(int), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty FivePrimeStartNucleotideIndexProperty = FivePrimeStartNucleotideIndexPropertyKey.DependencyProperty;

        public int FivePrimeStartNucleotideIndex
        {
            get { return (int)GetValue(FivePrimeStartNucleotideIndexProperty); }
            private set { SetValue(FivePrimeStartNucleotideIndexPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey FivePrimeEndNucleotideIndexPropertyKey = DependencyProperty.RegisterReadOnly("FivePrimeEndNucleotideIndex",
            typeof(int), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty FivePrimeEndNucleotideIndexProperty = FivePrimeEndNucleotideIndexPropertyKey.DependencyProperty;

        public int FivePrimeEndNucleotideIndex
        {
            get { return (int)GetValue(FivePrimeEndNucleotideIndexProperty); }
            private set { SetValue(FivePrimeEndNucleotideIndexPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey ThreePrimeStartNucleotideIndexPropertyKey = DependencyProperty.RegisterReadOnly("ThreePrimeStartNucleotideIndex",
            typeof(int), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty ThreePrimeStartNucleotideIndexProperty = ThreePrimeStartNucleotideIndexPropertyKey.DependencyProperty;

        public int ThreePrimeStartNucleotideIndex
        {
            get { return (int)GetValue(ThreePrimeStartNucleotideIndexProperty); }
            private set { SetValue(ThreePrimeStartNucleotideIndexPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey ThreePrimeEndNucleotideIndexPropertyKey = DependencyProperty.RegisterReadOnly("ThreePrimeEndNucleotideIndex",
            typeof(int), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty ThreePrimeEndNucleotideIndexProperty = ThreePrimeEndNucleotideIndexPropertyKey.DependencyProperty;

        public int ThreePrimeEndNucleotideIndex
        {
            get { return (int)GetValue(ThreePrimeEndNucleotideIndexProperty); }
            private set { SetValue(ThreePrimeEndNucleotideIndexPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey ContextMessagePropertyKey = DependencyProperty.RegisterReadOnly("ContextMessage",
            typeof(string), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(string.Empty));
        public static readonly DependencyProperty ContextMessageProperty = ContextMessagePropertyKey.DependencyProperty;

        public string ContextMessage
        {
            get { return (string)GetValue(ContextMessageProperty); }
            private set { SetValue(ContextMessagePropertyKey, value); }
        }

        public static readonly DependencyProperty BoundingBoxStrokeProperty = DependencyProperty.Register("BoundingBoxStroke",
            typeof(Brush), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.LightBlue), FrameworkPropertyMetadataOptions.None, OnBoundingBoxVisualPropertyChanged));

        public Brush BoundingBoxStroke
        {
            get { return (Brush)GetValue(BoundingBoxStrokeProperty); }
            set { SetValue(BoundingBoxStrokeProperty, value); }
        }

        public static readonly DependencyProperty BoundingBoxStrokeThicknessProperty = DependencyProperty.Register("BoundingBoxStrokeThickness",
            typeof(double), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(1.5, FrameworkPropertyMetadataOptions.None, OnBoundingBoxVisualPropertyChanged));

        public double BoundingBoxStrokeThickness
        {
            get { return (double)GetValue(BoundingBoxStrokeThicknessProperty); }
            set { SetValue(BoundingBoxStrokeThicknessProperty, value); }
        }

        private static readonly DependencyPropertyKey LocationPropertyKey = DependencyProperty.RegisterReadOnly("Location",
            typeof(Point), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata(OnRenderingPropertyChanged));
        public static readonly DependencyProperty LocationProperty = LocationPropertyKey.DependencyProperty;

        public Point Location
        {
            get { return (Point)GetValue(LocationProperty); }
            private set { SetValue(LocationPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey RotationAnglePropertyKey = DependencyProperty.RegisterReadOnly("RotationAngle",
            typeof(double), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty RotationAngleProperty = RotationAnglePropertyKey.DependencyProperty;

        public double RotationAngle
        {
            get { return (double)GetValue(RotationAngleProperty); }
            private set { SetValue(RotationAnglePropertyKey, value); }
        }

        private static readonly DependencyPropertyKey BoundingBoxPropertyKey = DependencyProperty.RegisterReadOnly("BoundingBox",
            typeof(Rect), typeof(HelixStructureElementVisualComponent), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty BoundingBoxProperty = BoundingBoxPropertyKey.DependencyProperty;

        public Rect BoundingBox
        {
            get { return (Rect)GetValue(BoundingBoxProperty); }
            private set { SetValue(BoundingBoxPropertyKey, value); }
        }

        private static void OnComponentVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            HelixStructureElementVisualComponent helixVisual = d as HelixStructureElementVisualComponent;
            if (helixVisual != null)
            {
                helixVisual.PropagateVisualPropertyChanged();
            }
        }

        private static void OnBoundingBoxVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        }

        private static void OnRenderingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            HelixStructureElementVisualComponent helixVisual = d as HelixStructureElementVisualComponent;
            if (helixVisual != null)
            {
                if (e.Property == LocationProperty ||
                    e.Property == RotationAngleProperty)
                {
                    helixVisual.PhysicalHelixVisual.Measure();
                    helixVisual.PhysicalHelixVisual.Render();
                }
            }
        }

        #endregion

        #region Properties

        public Rect Bounds
        {
            get;
            private set;
        }

        public bool IsSelected
        {
            get { return _isSelected; }
            private set
            {
                if (!IsSelectable && value)
                    return;
                _isSelected = value;
                PhysicalHelixVisual.Render();
            }
        }

        public bool HideBoundingBox
        {
            get { return _hideBoundingBox; }
            set
            {
                _hideBoundingBox = value;
                PhysicalHelixVisual.Render();
            }
        }

        public List<Point> Edges
        {
            get { return _edges; }
        }

        #endregion

        #region IStructureElementVisualComponent Members

        public IEnumerable<int> NucleotideIndices
        {
            get
            {
                IEnumerable<int> allFivePrimeNtIdx = Enumerable.Range(FivePrimeStartNucleotideIndex, (FivePrimeEndNucleotideIndex - FivePrimeStartNucleotideIndex) + 1);
                IEnumerable<int> allThreePrimeNtIdx = Enumerable.Range(ThreePrimeStartNucleotideIndex, (ThreePrimeEndNucleotideIndex - ThreePrimeStartNucleotideIndex) + 1);
                return allFivePrimeNtIdx.Union(allThreePrimeNtIdx);
            }
        }

        public Visual MakeVisualSnapshot()
        {
            //Since this visual copy is basically for printing and screen captures, we return null
            //becuase the helix has no rendering components except when it is selected. In contrast
            //a base pair component has a connector and a nucleotide component has the nucleotide representation (text, circle, rectangle).
            return null;
        }

        public Visual VisualComponent
        {
            get { return PhysicalHelixVisual; }
        }

        public bool IsComponentVisible
        {
            get { return IsSelected; }
            set {}
        }

        private IStructureElementVisualComponent _parentComponent;
        public IStructureElementVisualComponent ParentComponent
        {
            get { return _parentComponent; }
            set
            {
                _parentComponent = value;
                NotifyParentHierarchyChanged();
            }
        }

        public IEnumerable<IStructureElementVisualComponent> Children
        {
            get { return ChildrenInternal.AsEnumerable<IStructureElementVisualComponent>(); }
        }

        public FontFamily NucleotideFontFamily
        {
            get
            {
                FontFamily val = GetValue(NucleotideFontFamilyProperty) as FontFamily;
                if (val != null)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFontFamily;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideFontFamilyProperty, value); }
        }

        public FontWeight? NucleotideFontWeight
        {
            get
            {
                FontWeight? val = GetValue(NucleotideFontWeightProperty) as FontWeight?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFontWeight;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideFontWeightProperty, value); }
        }

        public FontStyle? NucleotideFontStyle
        {
            get
            {
                FontStyle? val = GetValue(NucleotideFontStyleProperty) as FontStyle?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFontStyle;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideFontStyleProperty, value); }
        }

        public double? NucleotideFontSize
        {
            get
            {
                double? val = GetValue(NucleotideFontSizeProperty) as double?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFontSize;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideFontSizeProperty, value); }
        }

        public NucleotideRenderingMode? NucleotideRenderingStyle
        {
            get
            {
                NucleotideRenderingMode? val = GetValue(NucleotideRenderingStyleProperty) as NucleotideRenderingMode?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideRenderingStyle;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideRenderingStyleProperty, value); }
        }

        public Brush NucleotideFill
        {
            get
            {
                Brush val = GetValue(NucleotideFillProperty) as Brush;
                if (val != null)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFill;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideFillProperty, value); }
        }

        public char? NucleotideSubstitution
        {
            get
            {
                char? val = GetValue(NucleotideSubstitutionProperty) as char?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideSubstitution;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideSubstitutionProperty, value); }
        }

        public bool? IsNucleotideSubstituted
        {
            get
            {
                bool? val = GetValue(IsNucleotideSubstitutedProperty) as bool?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.IsNucleotideSubstituted;
                    else
                        return null;
                }
            }
            set { SetValue(IsNucleotideSubstitutedProperty, value); }
        }

        public double? NucleotideCircleDiameter
        {
            get
            {
                double? val = GetValue(NucleotideCircleDiameterProperty) as double?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleDiameter;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideCircleDiameterProperty, value); }
        }

        public double? NucleotideCircleEdgeThickness
        {
            get
            {
                double? val = GetValue(NucleotideCircleEdgeThicknessProperty) as double?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleEdgeThickness;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideCircleEdgeThicknessProperty, value); }
        }

        public bool? NucleotideCircleIsFilled
        {
            get
            {
                bool? val = GetValue(NucleotideCircleIsFilledProperty) as bool?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleIsFilled;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideCircleIsFilledProperty, value); }
        }

        public Brush NucleotideCircleFill
        {
            get
            {
                Brush val = (Brush)GetValue(NucleotideCircleFillProperty);
                if (val != null)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleFill;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideCircleFillProperty, value); }
        }

        public Brush NucleotideCircleStroke
        {
            get
            {
                Brush val = (Brush)GetValue(NucleotideCircleStrokeProperty);
                if (val != null)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleStroke;
                    else
                        return null;
                }
            }
            set { SetValue(NucleotideCircleStrokeProperty, value); }
        }

        public bool? BasePairConnectorIsFilled
        {
            get 
            {
                bool? val = GetValue(BasePairConnectorIsFilledProperty) as bool?;
                if (!val.HasValue)
                {
                    try
                    {
                        //If were not explicitly set, we look up the food chain.
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorIsFilled;
                    }
                    catch (NotSupportedException) { }
                    return null;
                }
                else { return val; }
            }
            set { SetValue(BasePairConnectorIsFilledProperty, value); }
        }

        public bool? BasePairConnectorIsVisible
        {
            get 
            {
                bool? val = GetValue(BasePairConnectorIsVisibleProperty) as bool?;
                if (!val.HasValue)
                {
                    try
                    {
                        //If were not explicitly set, we look up the food chain.
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorIsVisible;
                    }
                    catch (NotSupportedException) { }
                    return null;
                }
                else { return val; }
            }
            set { SetValue(BasePairConnectorIsVisibleProperty, value); }
        }

        public Brush BasePairConnectorFill
        {
            get 
            {
                Brush val = GetValue(BasePairConnectorFillProperty) as Brush;
                if (val == null)
                {
                    try
                    {
                        //If were not explicitly set, we look up the food chain.
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorFill;
                    }
                    catch (NotSupportedException) { }
                    return null;
                }
                else { return val; }
            }
            set { SetValue(BasePairConnectorFillProperty, value); }
        }

        public Brush BasePairConnectorStroke
        {
            get 
            {
                Brush val = GetValue(BasePairConnectorStrokeProperty) as Brush;
                if (val == null)
                {
                    try
                    {
                        //If were not explicitly set, we look up the food chain.
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorStroke;
                    }
                    catch (NotSupportedException) { }
                    return null;
                }
                else { return val; }
            }
            set { SetValue(BasePairConnectorStrokeProperty, value); }
        }

        public double? BasePairConnectorStrokeThickness
        {
            get 
            {
                double? val = GetValue(BasePairConnectorStrokeThicknessProperty) as double?;
                if (!val.HasValue)
                {
                    try
                    {
                        //If were not explicitly set, we look up the food chain.
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorStrokeThickness;
                    }
                    catch (NotSupportedException) { }
                    return null;
                }
                else { return val; }
            }
            set { SetValue(BasePairConnectorStrokeThicknessProperty, value); }
        }

        public double? BasePairConnectorSize
        {
            get 
            {
                double? val = GetValue(BasePairConnectorSizeProperty) as double?;
                if (!val.HasValue)
                {
                    try
                    {
                        //If were not explicitly set, we look up the food chain.
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorSize;
                    }
                    catch (NotSupportedException) { }
                    return null;
                }
                else { return val; }
            }
            set { SetValue(BasePairConnectorSizeProperty, value); }
        }

        public BasePairConnectorRenderingMode? BasePairConnectorRenderingStyle
        {
            get 
            {
                BasePairConnectorRenderingMode? val = GetValue(BasePairConnectorRenderingStyleProperty) as BasePairConnectorRenderingMode?;
                if (!val.HasValue)
                {
                    try
                    {
                        //If were not explicitly set, we look up the food chain.
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorRenderingStyle;
                    }
                    catch (NotSupportedException) { }
                    return null;
                }
                else { return val; }
            }
            set { SetValue(BasePairConnectorRenderingStyleProperty, value); }
        }

        public void NotifyParentHierarchyChanged()
        {
            foreach (var child in ChildrenInternal)
                child.NotifyParentHierarchyChanged();
        }

        public void PropagateVisualPropertyChanged()
        {
            foreach (var child in ChildrenInternal)
                child.PropagateVisualPropertyChanged();
        }

        public void AddChild(IStructureElementVisualComponent component)
        {
            if (component != null && !ChildrenInternal.Contains(component))
            {
                ChildrenInternal.Add(component);
                component.ParentComponent = this;
            }
        }

        public void RemoveChild(IStructureElementVisualComponent component)
        {
            if (component != null && ChildrenInternal.Contains(component))
            {
                ChildrenInternal.Remove(component);
                component.ParentComponent = null;
            }
        }

        public bool HitTest(Point point, bool includeChildren)
        {
            if (includeChildren)
            {
                foreach (var child in ChildrenInternal)
                {
                    if (child.HitTest(point, includeChildren))
                        return true;
                }
            }

            return false;
        }

        public void MakeSelectable()
        {
            IsSelectable = true;
            foreach (var child in ChildrenInternal)
                child.MakeSelectable();
        }

        public void MakeUnselectable()
        {
            IsSelectable = false;
            foreach (var child in ChildrenInternal)
                child.MakeUnselectable();
        }

        public void Select(bool showNucleotideConnectors)
        {
            IsSelected = true;
            foreach (var child in ChildrenInternal)
                child.Select(showNucleotideConnectors);
        }

        public void Deselect()
        {
            IsSelected = false;
            foreach (var child in ChildrenInternal)
                child.Deselect();
        }

        public void ContextuallySelect()
        {
        }

        public void ContextuallyDeselect()
        {
        }

        public bool IsNucleotideFillInheriting()
        {
            var fill = GetValue(NucleotideFillProperty);
            return (fill == null) ? true : false;
        }

        public bool IsNucleotideCircleFillInheriting()
        {
            var fill = GetValue(NucleotideFillProperty);
            return (fill == null) ? true : false;
        }

        public bool IsNucleotideCircleStrokeInheriting()
        {
            var fill = GetValue(NucleotideFillProperty);
            return (fill == null) ? true : false;
        }

        public bool IsBasePairConnectorFillInheriting()
        {
            var connfill = GetValue(BasePairConnectorFillProperty);
            return (connfill == null) ? true : false;
        }

        public bool IsBasePairConnectorStrokeInheriting()
        {
            var connstroke = GetValue(BasePairConnectorStrokeProperty);
            return (connstroke == null) ? true : false;
        }

        public void StartEditTracking()
        {
            throw new NotImplementedException();
        }

        public void InterruptEditTracking()
        {
            throw new NotImplementedException();
        }

        public void UninterruptEditTracking()
        {
            throw new NotImplementedException();
        }

        public void ApplyPropertyEditSet(StructureElementPropertyChangeSet set, bool interruptEditTracking)
        {
            throw new NotImplementedException();
        }

        public void UndoAllPropertyChanges()
        {
            throw new NotImplementedException();
        }

        public void UndoLastPropertyChange()
        {
            throw new NotImplementedException();
        }

        public object SaveProperties()
        {
            throw new NotImplementedException();
        }

        public bool IsDirty()
        {
            throw new NotImplementedException();
        }

        #endregion

        public Point GetLocationForNucleotide(int nucleotideIndex)
        {
            if (NucleotideIndices.Contains(nucleotideIndex))
                return RenderingRectangle.AllLocations[nucleotideIndex];
            throw new ArgumentException(string.Format("Error: Nucleotide Index: {0} is not within the helix {1}", nucleotideIndex, FivePrimeStartNucleotideIndex));
        }

        public void HelixNucleotideMovedTo(int nucleotideIndex, Point location)
        {
            if (NucleotideIndices.Contains(nucleotideIndex))
            {
                RenderingRectangle.MoveNucleotideTo(nucleotideIndex, location);
                PropagateRenderingRectangleUpdates();
                return;
            }
            else
            {
                throw new ArgumentException(string.Format("Error: Failed Nucleotide move within helix because Nucleotide {0} is not within the helix {1}", nucleotideIndex, FivePrimeStartNucleotideIndex));
            }
        }

        public void HelixNucleotideMovedBy(int nucleotideIndex, Point delta)
        {
            if (NucleotideIndices.Contains(nucleotideIndex))
            {
                RenderingRectangle.MoveNucleotideBy(nucleotideIndex, delta);
                PropagateRenderingRectangleUpdates();
                return;
            }
            else
            {
                throw new ArgumentException(string.Format("Error: Failed Nucleotide move within helix because Nucleotide {0} is not within the helix {1}", nucleotideIndex, FivePrimeStartNucleotideIndex));
            }
        }

        public void HelixBasePairMovedTo(int fivePrimeNucleotideIndex, int threePrimeNucleotideIndex, Point location)
        {
            if (NucleotideIndices.Contains(fivePrimeNucleotideIndex) && NucleotideIndices.Contains(threePrimeNucleotideIndex))
            {
                
            }
            else
            {
                throw new ArgumentException(string.Format("Error: Failed Base Pair move within helix becuase BasePair {0} is not within helix {1}", fivePrimeNucleotideIndex, FivePrimeStartNucleotideIndex));
            }
        }

        public void HelixBasePairMovedBy(int fivePrimeNucleotideIndex, int threePrimeNucleotideIndex, Point delta)
        {
            if (NucleotideIndices.Contains(fivePrimeNucleotideIndex) && NucleotideIndices.Contains(threePrimeNucleotideIndex))
            {
                RenderingRectangle.MoveNucleotideBy(fivePrimeNucleotideIndex, delta);
                RenderingRectangle.MoveNucleotideBy(threePrimeNucleotideIndex, delta);
                PropagateRenderingRectangleUpdates();
            }
            else
            {
                throw new ArgumentException(string.Format("Error: Failed Base Pair move within helix becuase BasePair {0} is not within helix {1}", fivePrimeNucleotideIndex, FivePrimeStartNucleotideIndex));
            }
        }

        /// <summary>
        /// This function propagates the move to any underlying rendering logic
        /// </summary>
        /// <param name="location"></param>
        public void MoveTo(Point location)
        {
            Point delta = new Point(location.X - Location.X, location.Y - Location.Y);
            MoveBy(delta);
        }

        /// <summary>
        /// This function propagates the move to any underlying rendering logic
        /// </summary>
        /// <param name="delta"></param>
        public void MoveBy(Point delta)
        {
            RenderingRectangle.MoveBy(delta);
            PropagateRenderingRectangleUpdates();
        }

        public Dictionary<int, Point> ChangeHelixPadding(double bpPadding)
        {
            Dictionary<int, Point> bpUpdates = RenderingRectangle.SnapToOpeningBasePair(bpPadding);
            PropagateRenderingRectangleUpdates();
            return bpUpdates;
        }

        public Dictionary<int, Point> SnapToClosingBasePair()
        {
            Dictionary<int, Point> bpUpdates = RenderingRectangle.SnapToClosingBasePair(DEFAULT_BASEPAIR_PADDING);
            PropagateRenderingRectangleUpdates();
            return bpUpdates;
        }

        public Dictionary<int, Point> SnapToOpeningBasePair()
        {
            Dictionary<int, Point> bpUpdates = RenderingRectangle.SnapToOpeningBasePair(DEFAULT_BASEPAIR_PADDING);
            PropagateRenderingRectangleUpdates();
            return bpUpdates;
        }

        public HelixStructureElementVisualComponent(HelixRectangle renderingRectangle, int fivePrimeStart, int fivePrimeEnd,
            int threePrimeStart, int threePrimeEnd, IEnumerable<IBasePairBioEntity> basePairs, IEnumerable<IBioSymbol> fivePrimeStrand,
            IEnumerable<IBioSymbol> threePrimeStrand, bool isPseudoknotted)
        {
            Debug.Assert(renderingRectangle != null);
            //RenderingRectangle = renderingRectangle;
            FivePrimeStartNucleotideIndex = fivePrimeStart;
            FivePrimeEndNucleotideIndex = fivePrimeEnd;
            ThreePrimeStartNucleotideIndex = threePrimeStart;
            ThreePrimeEndNucleotideIndex = threePrimeEnd;
            BasePairs = basePairs;
            FivePrimeStrand = fivePrimeStrand;
            ThreePrimeStrand = threePrimeStrand;
            IsPseudoknotted = isPseudoknotted;
            ChildrenInternal = new List<IStructureElementVisualComponent>();
            PhysicalHelixVisual = new HelixDrawingVisual(this);
            SetRenderingRectangle(renderingRectangle);
            Debug.Assert(NucleotideIndices.Count(idx => RenderingRectangle.AllLocations.ContainsKey(idx)) == NucleotideIndices.Count());
            //Bounds = PhysicalHelixVisual.Measure();
            BoundingBox = Bounds;
            ContextMessage = BuildContextMessage();
        }

        private HelixDrawingVisual PhysicalHelixVisual { get; set; }
        private HelixRectangle RenderingRectangle { get; set; }
        private List<IStructureElementVisualComponent> ChildrenInternal { get; set; }
        private bool _isSelected = false;
        private bool _hideBoundingBox = false;
        private List<Point> _edges = new List<Point>();

        private static double DEFAULT_BASEPAIR_PADDING = 8.0;

        private bool _isSelectable = false;
        private bool IsSelectable
        {
            get { return _isSelectable; }
            set
            {
                bool newSelectableState = value;
                if (!newSelectableState && IsSelected)
                    IsSelected = false; //Clear the selection flag if selected
                _isSelectable = newSelectableState;
            }
        }

        private void PropagateRenderingRectangleUpdates()
        {
            Location = RenderingRectangle.FivePrimeStartNucleotideLocation;
            RotationAngle = RenderingRectangle.RotationAngle;
            Edges.Clear();
            Edges.Add(RenderingRectangle.FivePrimeStartNucleotideLocation);
            Edges.Add(RenderingRectangle.FivePrimeEndNucleotideLocation);
            Edges.Add(RenderingRectangle.ThreePrimeStartNucleotideLocation);
            Edges.Add(RenderingRectangle.ThreePrimeEndNucleotideLocation);
            Bounds = PhysicalHelixVisual.Measure();
            BoundingBox = Bounds;
            PhysicalHelixVisual.Render();
        }

        private void SetRenderingRectangle(HelixRectangle newRenderingVector)
        {
            Debug.Assert(newRenderingVector != null);
            RenderingRectangle = newRenderingVector;
            Edges.Clear();
            Edges.Add(RenderingRectangle.FivePrimeStartNucleotideLocation);
            Edges.Add(RenderingRectangle.FivePrimeEndNucleotideLocation);
            Edges.Add(RenderingRectangle.ThreePrimeStartNucleotideLocation);
            Edges.Add(RenderingRectangle.ThreePrimeEndNucleotideLocation);
            Location = RenderingRectangle.FivePrimeStartNucleotideLocation;
            RotationAngle = RenderingRectangle.RotationAngle;
        }

        private string BuildContextMessage()
        {
            return string.Format("Helix with {0} Base Pairs; 5' strand from {1} to {2} and 3' strand from {3} to {4}", BasePairs.Count(), FivePrimeStartNucleotideIndex + 1,
                FivePrimeEndNucleotideIndex + 1, ThreePrimeStartNucleotideIndex + 1, ThreePrimeEndNucleotideIndex + 1);
        }
    }
}
